/*
Copyright (C) 2007 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @fileoverview Call that handles the communication to Twitter. This includes
 * validating user credentials, updating the users status, and downloading
 * the most recent messages from friends
 */

/**
 * Connection string for updating your own message
 * @type String
 */
var UPDATE_MESSAGE_CONNECTION_STRING =
    'https://twitter.com/statuses/update.json';
/**
 * Connection string for validating credentials
 * @type String
 */
var VALIDATE_CONNECTION_STRING =
    'https://twitter.com/account/verify_credentials.json';
/**
 * Connection string for requesting your friends messages
 * @type String
 */
var FRIENDS_UPDATE_CONNECTION_STRING =
    'https://twitter.com/statuses/friends_timeline.json';
/**
 * Used to keep track of the number of new messages when we recieve
 * a list of messages from Twitter.
 * @type Number
 */
var numNewMessages;

/**
 * Class representing all of the twitter info for a single user. Including the
 * username, password, list of messages from their friends, and their login
 * status
 * @constructor
 */
function TwitterInfo() {
  this.username = "";
  this.password = "";
  this.allMessages = new Array();
  this.isLoggedIn = false;
}

/**
 * Represents a single message in twitter. It contains a unique
 * id, the name of the user who created the message, the text of the message,
 * and the url for the image of the user.
 * @param {Number} id The inuque ID for this message
 * @param {String} name The username of the message author
 * @param {String} text The text of the message
 * @param {String} imageurl URL for the image of the author of the message
 * @constructor
 */
function TwitterMessage(id, name, text, imageurl) {
  this.id = id;
  this.name = name;
  this.text = text;
  this.imageurl = imageurl;
}

/**
 * Send query to twitter with credentials to check if the user information is
 * valid. Returns true to the callback function if the credentials are valid,
 * false otherwise
 * @param {TwitterInfo} twitterUserInfo Twitter user information object
 * @param {Function} callback Callback function
 */
function isLoggedIn(twitterUserInfo, callback) {
  var request = createRequest(TWITTER_GET,
      VALIDATE_CONNECTION_STRING,
      twitterUserInfo.username,
      twitterUserInfo.password);

  request.onreadystatechange = function() {
    if (request.readyState == 4) {
      if (request.status == 200) {
        callback(true);
      } else {
        callback(false)
      }

      request = null;
    }
  };

  try {
    request.send();
  } catch (e) {}
}

/**
 * Get the most recent messages from the users friends in the last 24 hours.
 * Takes in the username, password, array of existing messages, maximum number
 * of messages, and a callback function. Once we get a response from Twitter
 * with the new messages, we call parseMessages which will take the messages
 * and add them to the array of existing messages. The callback method is called
 * both on success and on failure, it returns with the status (to identify what
 * the error was, if there was one) and the number of new messages we received
 * (if any).
 * @param {TwitterInfo} twitterUserInfo Twitter user information object
 * @param {Number} maxMessages Maximum number of messages to have
 * @param {Function} callback Callback function
 */
function getLatestMessages(twitterUserInfo,
                           maxMessages,
                           statusMessageCallback) {
  numNewMessages = 0;
  var request = createRequest(TWITTER_GET,
      FRIENDS_UPDATE_CONNECTION_STRING,
      twitterUserInfo.username,
      twitterUserInfo.password);

  request.setRequestHeader("If-Modified-Since",
                           "Mon, 1 Jan 2000 00:00:00 GMT" );
  request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status == 200) {
      try {
        parseMessages(eval(request.responseText),
                      twitterUserInfo,
                      maxMessages);
        statusMessageCallback(request.status, numNewMessages);
      } catch (e) {
        debug.trace("parseMessages fail");
        // If there is a problem eval'ing return an error and don't update.
        statusMessageCallback("404");
      }

      request = null;
    } else if (request.readyState == 4) {
      statusMessageCallback(request.status);

      request = null;
    }
  };

  try {
    request.send(null);
  } catch (e) {}
}

/**
 * Parses the JSON object returned from twitter. Adds the new messages to the
 * messagesArray making sure not to add more than 'maxMessages' number of
 * messages.
 * @param {Object} messageData JSON data returned from Twitter
 * @param {TwitterInfo} twitterUserInfo Twitter user information object
 * @param {Number} maxMessages Maximum number of messages to have
 */
function parseMessages(messageData, twitterUserInfo, maxMessages) {
  var previous_max_id = twitterUserInfo.max_id;

  for (var i = 0; i < messageData.length; ++i) {
    if (messageData[i].id <= previous_max_id) {
      // older messages may be retrieved if the user adds
      // a new person to follow; we do not handle displaying
      // things correctly in this case, so skip any older
      // messages
      // debug.trace('old message id ' + messageData[i].id);
    } else if (isNewItem(twitterUserInfo, messageData[i].id)) {
      ++numNewMessages;
    }

    // update max message id seen so far
    if (messageData[i].id > twitterUserInfo.max_id) {
      twitterUserInfo.max_id = messageData[i].id;
    }
  }

  var theMaximum = numNewMessages;
  if (numNewMessages > maxMessages) {
    theMaximum = maxMessages;
  }

  for (var j = 0; j < theMaximum; ++j) {
    if (messageData[j].id !== null &&
        messageData[j].user.screen_name !== null &&
        messageData[j].text !== null &&
        messageData[j].user.profile_image_url !== null) {

        twitterUserInfo.allMessages.splice(j, 0, new TwitterMessage(
          messageData[j].id,
          messageData[j].user.screen_name,
          messageData[j].text,
          messageData[j].user.profile_image_url));
    }
  }
}

/**
 * Posts a new message to twitter. The user must be logged in correctly,
 * otherwise the gadget will clear and show the login page again so the user
 * can type in the correct credentials.
 * @param {TwitterInfo} twitterUserInfo Twitter user information object
 * @param {String} message The message to upload
 * @param {Function} statusMessageCallback Callback function
 */
function updateTwitterMessage(twitterUserInfo, message, statusMessageCallback) {
  if (message !== "") {
    var request = createRequest(TWITTER_POST,
        UPDATE_MESSAGE_CONNECTION_STRING,
        twitterUserInfo.username,
        twitterUserInfo.password);

    request.onreadystatechange = function() {
        if (request.readyState == 4 && request.status == 200) {
          statusMessageCallback(request.status);
          request = null;
        } else if (request.readyState == 4) {
          statusMessageCallback(request.status);
          request = null;
        }
      };

    try {
      request.send("status="+encodeURIComponent(message));
    } catch (e) {}
  }
}

/**
 * Makes a call to twitter to collect the image for the user (given by imageUrl)
 * asynchronously and displays them as they are received
 * @param {String} imageUrl URL of the image
 * @param {Number} statusId ID of the message to assosiate this image with
 */
function setStatusImage(imageUrl, element) {
  var request = createRequest(TWITTER_GET, imageUrl);
  request.setRequestHeader("If-Modified-Since",
                           "Mon, 1 Jan 2000 00:00:00 GMT" );
  request.onreadystatechange = function () {
    if (request.readyState == 4) {
      if (request.status == 200) {
        var image = request.responseStream;
        element.src = image;
      } else if (request.status == 404) {
        gadget.debug.error("Twitter Error: Couldn't connect to Twitter.");
      } else if (request.status == 401) {
        gadget.debug.error("Twitter Error: Authentication error.");
      } else if (request.status >= 500) {
        gadget.debug.error("Twitter Error: A server error occured.");
      } else {
        gadget.debug.error("Twitter Error: An unknown error occured.");
      }

      request = null;
    }
  };

  request.send();
}

/**
 * Check if the given ID has already been seen (that is, exists in the
 * messages array already)
 * @param {TwitterInfo} twitterUserInfo Twitter user information object
 * @param {Number} id The id of the message to check if it exists
 * @return {Bool} Returns true if the item exists, false otherwise
 */
function isNewItem(twitterUserInfo, id) {
  var isNew = true;
  for (var i = 0; i < twitterUserInfo.allMessages.length; ++i) {
    if (twitterUserInfo.allMessages[i].id == id) {
      isNew = false;
    }
  }

  return isNew;
}

var TWITTER_GET = "GET";
var TWITTER_POST = "POST";

/**
 * HTTPRequest factory.
 */
function createRequest(method, url, opt_username, opt_password) {
  var request;
  if (framework.runtime.osName.match(/windows/i) != null)
    request = framework.google.betaXmlHttpRequest();
  else
    request = new XMLHttpRequest;

  if (opt_username || opt_password) {
    request.open(method, url, true, opt_username, opt_password);
  } else {
    request.open(method, url, true);
  }

  return request;
}
